/**
 * @docauthor Robert Dougan <rob@sencha.com>
 *
 * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
 * {@link #iconCls icons}, {@link #cfg-menu dropdown menus}, {@link #tooltip tooltips}
 * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
 * a user clicks the button, or use {@link #listeners listeners} for other events such as
 * {@link #mouseover mouseover}. Example usage:
 *
 *     @example
 *     Ext.create('Ext.Button', {
 *         text: 'Click me',
 *         renderTo: Ext.getBody(),
 *         handler: function() {
 *             alert('You clicked the button!');
 *         }
 *     });
 *
 * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
 * method.  Example usage:
 *
 *     @example
 *     Ext.create('Ext.Button', {
 *         text    : 'Dynamic Handler Button',
 *         renderTo: Ext.getBody(),
 *         handler : function() {
 *             // this button will spit out a different number every time you click it.
 *             // so firstly we must check if that number is already set:
 *             if (this.clickCount) {
 *                 // looks like the property is already set, so lets just add 1 to that number and alert the user
 *                 this.clickCount++;
 *                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
 *             } else {
 *                 // if the clickCount property is not set, we will set it and alert the user
 *                 this.clickCount = 1;
 *                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
 *             }
 *         }
 *     });
 *
 * A button within a container:
 *
 *     @example
 *     Ext.create('Ext.Container', {
 *         renderTo: Ext.getBody(),
 *         items   : [
 *             {
 *                 xtype: 'button',
 *                 text : 'My Button'
 *             }
 *         ]
 *     });
 *
 * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
 *
 * - `'small'`
 * - `'medium'`
 * - `'large'`
 *
 * Example usage:
 *
 *     @example
 *     Ext.create('Ext.Button', {
 *         renderTo: document.body,
 *         text    : 'Click me',
 *         scale   : 'large'
 *     });
 *
 * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
 * Example usage:
 *
 *     @example
 *     Ext.create('Ext.Button', {
 *         renderTo: Ext.getBody(),
 *         text: 'Click Me',
 *         enableToggle: true
 *     });
 *
 * You can assign a menu to a button by using the {@link #cfg-menu} configuration. This standard configuration
 * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
 * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
 * added to the button.  You can change the alignment of the arrow using the {@link #arrowAlign} configuration
 * on button.  Example usage:
 *
 *     @example
 *     Ext.create('Ext.Button', {
 *         text      : 'Menu button',
 *         renderTo  : Ext.getBody(),
 *         arrowAlign: 'bottom',
 *         menu      : [
 *             {text: 'Item 1'},
 *             {text: 'Item 2'},
 *             {text: 'Item 3'},
 *             {text: 'Item 4'}
 *         ]
 *     });
 *
 * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
 * configuration or using the {@link #addListener} method.  Button has a variety of different listeners:
 *
 * - `click`
 * - `toggle`
 * - `mouseover`
 * - `mouseout`
 * - `mouseshow`
 * - `menuhide`
 * - `menutriggerover`
 * - `menutriggerout`
 *
 * Example usage:
 *
 *     @example
 *     Ext.create('Ext.Button', {
 *         text     : 'Button',
 *         renderTo : Ext.getBody(),
 *         listeners: {
 *             click: function() {
 *                 // this == the button, as we are in the local scope
 *                 this.setText('I was clicked!');
 *             },
 *             mouseover: function() {
 *                 // set a new config which says we moused over, if not already set
 *                 if (!this.mousedOver) {
 *                     this.mousedOver = true;
 *                     alert('You moused over a button!\n\nI wont do this again.');
 *                 }
 *             }
 *         }
 *     });
 */
define(function(require) {
	require("../component")
	aries.klass("aries.button.Button", aries.Component.extend({
		events : {
			"mouseenter" : "onMouseEnter",
			"mouseleave" : "onMouseLeave"
		},
		/**
		 * @cfg {String} [baseCls='x-btn']
		 * The base CSS class to add to all buttons.
		 */
		baseCls : aries.baseCSSPrefix + 'btn',
		isButton : true,

		/**
		 * @property {Boolean} hidden
		 * True if this button is hidden.
		 * @readonly
		 */
		hidden : false,

		/**
		 * @property {Boolean} disabled
		 * True if this button is disabled.
		 * @readonly
		 */
		disabled : false,

		/**
		 * @cfg {String} overCls
		 * The CSS class to add to a button when it is in the over (hovered) state.
		 */
		overCls : 'over',

		/**
		 * @property {Boolean} pressed
		 * True if this button is pressed (only if enableToggle = true).
		 * @readonly
		 */
		pressed : false,
		renderTpl : ['<em id="<%=id%>-btnWrap"<%if(splitCls){%> class="<%=splitCls%>"<%}%>>', '<%if(href){%>', '<a id="<%=id%>-btnEl" href="<%=href%>" class="<%=btnCls%>" target="<%=hrefTarget%>"', '<%if(typeof(tabIndex)!=="undefined"){%> tabIndex="<%=tabIndex%>"<%}%>', '<%if(disabled){%> disabled="disabled"<%}%>', ' role="link">', '<span id="<%=id%>-btnInnerEl" class="<%=baseCls%>-inner">', '<%=text%>', '</span>', '<span id="<%=id%>-btnIconEl" class="<%=baseCls%>-icon <%=iconCls%>"<tpl if="iconUrl"> style="background-image:url(<%=iconUrl%>)"></span>', '</a>', '<%}else{%>', '<button id="<%=id%>-btnEl" type="<%=type%>" class="<%=btnCls%>" hidefocus="true"',
		// the autocomplete="off" is required to prevent Firefox from remembering
		// the button's disabled state between page reloads.
		'<%if(typeof(tabIndex)!=="undefined"){%> tabIndex="<%=tabIndex%>"<%}%>', '<%if(disabled){%> disabled="disabled"<%}%>', ' role="button" autocomplete="off">', '<span id="<%=id%>-btnInnerEl" class="<%=baseCls%>-inner" style="<%=innerSpanStyle%>">', '<%=text%>', '</span>', '<span id="<%=id%>-btnIconEl" class="<%=baseCls%>-icon <%=iconCls%>"<%if(typeof(iconUrl)!=="undefined"){%> style="background-image:url(<%=iconUrl%>)"<%}%>></span>', '</button>', '<%};%>', '</em>', '<%if(typeof(closable)!=="undefined"){%>', '<a id="<%=id%>-closeEl" href="#" class="<%=baseCls%>-close-btn" title="<%=closeText%>"></a>', '<%};%>'].join(''),

		/**
		 * @cfg {String} scale
		 * The size of the Button. Three values are allowed:
		 *
		 * - 'small' - Results in the button element being 16px high.
		 * - 'medium' - Results in the button element being 24px high.
		 * - 'large' - Results in the button element being 32px high.
		 */
		scale : 'small',
		/**
		 * @cfg {String} type
		 * The type of `<input>` to create: submit, reset or button.
		 */
		type : 'button',
		/**
		 * @cfg {String} iconAlign
		 * The side of the Button box to render the icon. Four values are allowed:
		 *
		 * - 'top'
		 * - 'right'
		 * - 'bottom'
		 * - 'left'
		 */
		iconAlign : 'left',

		/**
		 * @cfg {String} arrowAlign
		 * The side of the Button box to render the arrow if the button has an associated {@link #cfg-menu}. Two
		 * values are allowed:
		 *
		 * - 'right'
		 * - 'bottom'
		 */
		arrowAlign : 'right',

		/**
		 * @cfg {String} textAlign
		 * The text alignment for this button (center, left, right).
		 */
		textAlign : 'center',

		/**
		 * @private
		 * @property persistentPadding
		 * The padding spuriously added to a &lt;button> element which must be accounted for in the margins of the innerEl.
		 * This is calculated at first render time by creating a hidden button and measuring its insides.
		 */
		persistentPadding : undefined,

		/**
		 * @cfg {String} arrowCls
		 * The className used for the inner arrow element if the button has a menu.
		 */
		arrowCls : 'arrow',

		/**
		 * @cfg {Object} baseParams
		 * An object literal of parameters to pass to the url when the {@link #href} property is specified.
		 */

		/**
		 * @cfg {Object} params
		 * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
		 * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
		 */

		childEls : ['btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl'],

		beforeRender : function() {
			var me = this;

			aries.Component.prototype.beforeRender.apply(this, arguments);

			// Add all needed classes to the protoElement.
			me.oldCls = me.getComponentCls();
			me.addClsWithUI(me.oldCls);

			// Apply the renderData to the template args
			_.defaults(me.renderData, me.getTemplateArgs())
			//Ext.applyIf(me.renderData, me.getTemplateArgs());

			if (me.scale) {
				me.setScale(me.scale);
			}
		},

		// inherit docs
		disable : function(silent) {
			var me = this;

			aries.Component.prototype.disable.apply(this, arguments);

			if (me.btnEl) {
				me.btnEl.attr("disabled", true);
			}
			me.addClsWithUI('disabled');
			me.removeClsWithUI(me.overCls);

			// IE renders disabled text by layering gray text on top of white text, offset by 1 pixel. Normally this is fine
			// but in some circumstances (such as using formBind) it gets confused and renders them side by side instead.
			/*if (me.btnInnerEl && (Ext.isIE6 || Ext.isIE7)) {
			 me.btnInnerEl.repaint();
			 }*/

			return me;
		},

		// inherit docs
		enable : function(silent) {
			var me = this;

			aries.Component.prototype.enable.apply(this,arguments);

			if (me.btnEl) {
				me.btnEl.attr("disabled",false);
			}
			me.removeClsWithUI('disabled');

			return me;
		},
		// inherit docs
		initComponent : function() {
			aries.Component.prototype.initComponent.apply(this, arguments);
			this.addEvents(this.options.handler)
			if (this.menu) {
				// Flag that we'll have a splitCls
				this.split = true;

				// retrieve menu by id or instantiate instance if needed
				this.menu = Ext.menu.Manager.get(this.menu);
				this.menu.ownerButton = this;
			}

			// Accept url as a synonym for href
			if (this.url) {
				this.href = this.url;
			}

			// preventDefault defaults to false for links
			if (this.href && !this.hasOwnProperty('preventDefault')) {
				this.preventDefault = false;
			}

			if (_.isString(this.toggleGroup)) {
				this.enableToggle = true;
			}
		},

		getBtnCls : function() {
			return this.textAlign ? this.baseCls + '-' + this.textAlign : '';
		},

		getComponentCls : function() {
			var me = this, cls = [];

			// Check whether the button has an icon or not, and if it has an icon, what is the alignment
			if (me.iconCls || me.icon) {
				if (me.text) {
					cls.push('icon-text-' + me.iconAlign);
				} else {
					cls.push('icon');
				}
			} else if (me.text) {
				cls.push('noicon');
			}

			if (me.pressed) {
				cls.push(me.pressedCls);
			}
			return cls;
		},

		/**
		 * @private
		 * If there is a configured href for this Button, returns the href with parameters appended.
		 * @returns The href string with parameters appended.
		 */
		getHref : function() {
			var me = this, params = $.extend({}, me.baseParams);

			// write baseParams first, then write any params
			params = $.extend(params, me.params);
			return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
		},

		/**
		 * @private
		 * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
		 * element that cannot be removed. This method returns the size of that padding with a one-time detection.
		 * @return {Number[]} [top, right, bottom, left]
		 */
		getPersistentPadding : function() {
			var me = this, padding = me.persistentPadding, btn, leftTop, btnEl, btnInnerEl;

			// Create auto-size button offscreen and measure its insides
			// Short-circuit IE as it sometimes gives false positive for padding
			if (!padding) {
				padding = this.constructor.prototype.persistentPadding = [0, 0, 0, 0];
				/*if (!aries.isIE) {
				 btn = new aries.button.Button({
				 text : 'test',
				 style : 'position:absolute;top:-999px;'
				 });
				 btn.el = Ext.DomHelper.append(Ext.getBody(), btn.getRenderTree(), true);
				 btn.applyChildEls(btn.el);
				 btnEl = btn.btnEl;
				 btnInnerEl = btn.btnInnerEl;
				 btnEl.setSize(null, null);
				 //clear any hard dimensions on the button el to see what it does naturally
				 leftTop = btnInnerEl.getOffsetsTo(btnEl);
				 padding[0] = leftTop[1];
				 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
				 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
				 padding[3] = leftTop[0];

				 btn.destroy();
				 btn.el.remove();
				 }*/
			}
			return padding;
		},

		getSplitCls : function() {
			var me = this;
			return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
		},

		/**
		 * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
		 * create this Button's DOM structure.
		 *
		 * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
		 * their own implementation of this method.
		 *
		 * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
		 * {@link #template} returns an Object containing the following properties:
		 * @return {String} return.type The `<button>`'s {@link #type}
		 * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
		 * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
		 * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
		 * button's scale and icon alignment.
		 * @return {String} return.text The {@link #text} to display ion the Button.
		 * @return {Number} return.tabIndex The tab index within the input flow.
		 */
		getTemplateArgs : function() {
			var me = this, persistentPadding = me.getPersistentPadding(), innerSpanStyle = '';

			// Create negative margin offsets to counteract persistent button padding if needed
			if (Math.max.apply(Math, persistentPadding) > 0) {
				innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
					return -pad + 'px';
				}).join(' ');
			}

			return {
				href : me.getHref(),
				disabled : me.disabled,
				hrefTarget : me.hrefTarget,
				type : me.type,
				btnCls : me.getBtnCls(),
				splitCls : me.getSplitCls(),
				iconUrl : me.icon,
				iconCls : me.iconCls,
				text : me.text || '&#160;',
				tabIndex : me.tabIndex,
				innerSpanStyle : innerSpanStyle
			};
		},

		/**
		 * @private
		 * virtual mouseenter handler called when it is detected that the mouseout event
		 * signified the mouse entering the encapsulating element.
		 * @param e
		 */
		onMouseEnter : function(e) {
			if (!this.disabled/* && !e.within(me.el, true, true)*/) {
				this.addClsWithUI(this.overCls);
			}
			this.trigger('mouseenter', this, e);
		},

		/**
		 * @private
		 * virtual mouseleave handler called when it is detected that the mouseover event
		 * signified the mouse entering the encapsulating element.
		 * @param e
		 */
		onMouseLeave : function(e) {
			this.removeClsWithUI(this.overCls);
			this.trigger('mouseleave', this, e);
		},
		onClick : function() {
		},

		/**
		 * Method to change the scale of the button. See {@link #scale} for allowed configurations.
		 * @param {String} scale The scale to change to.
		 */
		setScale : function(scale) {
			var me = this, ui = me.ui.replace('-' + me.scale, '');

			//check if it is an allowed scale
			if (!$.inArray(me.allowedScales, scale)) {
				throw ('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
			}

			me.scale = scale;
			me.setUI(ui);
		},

		// inherit docs
		setUI : function(ui) {
			var me = this;

			//we need to append the scale to the UI, if not already done
			if (me.scale && !ui.match(me.scale)) {
				ui = ui + '-' + me.scale;
			}

			aries.Component.prototype.setUI.apply(this, [ui]);

			// Set all the state classNames, as they need to include the UI
			// me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
		},

		/**
		 * Sets this Button's text
		 * @param {String} text The button text
		 * @return {Ext.button.Button} this
		 */
		setText : function(text) {
			var me = this;
			me.text = text;
			me.btnInnerEl.text(text)
			return me;
		}
	}, {
		xtype : 'button'
	}))
})
